#include "viewer.h"
#include <QFile>
#include "geometryobjects.h"
#include "dcolordialog.h"
#include "propertyeditor.h"
#include "global.h"
#include "enums.h"

using namespace std;

void Viewer::setRenderProcess(RenderProcess *rp) { m_renderProcess = rp; }
void Viewer::setKeyFrame(KeyFrame *keyframe) { m_keyFrame = keyframe; }
void Viewer::setKeyFrameEditor(KeyFrameEditor *keyframeEditor) { m_keyFrameEditor = keyframeEditor; }

QImage Viewer::histogramImage1D() { return m_renderProcess->histogramImage1D(); }
QImage Viewer::histogramImage2D() { return m_renderProcess->histogramImage2D(); }
LightingInformation Viewer::lightInfo() { return m_renderProcess->lightInfo(); }
void Viewer::setLightInfo(LightingInformation lightInfo) { m_renderProcess->setLightInfo(lightInfo); }

void Viewer::toggleBounds() { m_renderProcess->toggleBounds(); }

Viewer::Viewer()
{
  m_saveSnapshots = false;
  m_imageMode = Enums::MonoImageMode;

  m_currFrame = 1;

  m_renderProcess = 0;
  setMouseTracking(true);  
}

Viewer::~Viewer() {}


void Viewer::init()
{
  QTimer::singleShot(1000, this, SLOT(glewInit()));

  // Restore previous viewer state.
  //restoreStateFromFile();

}

void
Viewer::switchOffAutoBufferSwapping()
{
  setAutoBufferSwap(false); // turn of automatic buffer swapping
}

void
Viewer::setLogger(QTextEdit *log)
{
  m_log = log;
  m_log->insertPlainText("initializing glew ...\n");
}

void
Viewer::resizeGL(int w, int h)
{
  QGLViewer::resizeGL(w, h);

  m_windowWidth = width();
  m_windowHeight = height();

  if (! GlewInit::initialised())
    return;

  if (m_renderProcess)
    m_renderProcess->setWindowSize(m_windowWidth, m_windowHeight);
}

void
Viewer::glewInit()
{
    GlewInit::initialise();
    QString glslVersion = (char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
    m_log->insertPlainText("GLSL version : "+glslVersion+"\n\n");


    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    if (m_renderProcess)
      {
	m_renderProcess->setLogger(m_log);
	m_renderProcess->setViewer(this);

	connect(this, SIGNAL(startRender()),
		m_renderProcess, SLOT(startRender()));

	connect(m_renderProcess, SIGNAL(switchOffAutoBufferSwapping()),
		this, SLOT(switchOffAutoBufferSwapping()));

	connect(m_renderProcess, SIGNAL(renderDone()),
		this, SLOT(renderDone()));

	m_renderProcess->setWindowSize(m_windowWidth, m_windowHeight);
      }
}

void Viewer::fastDraw()
{
  if (! isVisible())
    return;

  if (! GlewInit::initialised())
    return;

  makeCurrent();

  if (m_renderProcess)
    {
      int d, r, c;
      m_renderProcess->gridSize(d, r, c);
      if (d == 0)
	return;

      m_renderProcess->fastRender();
    }
}

void Viewer::draw()
{
  if (! isVisible())
    return;

  if (! GlewInit::initialised())
    return;

  makeCurrent();

  if (m_renderProcess)
    {
      int d, r, c;
      m_renderProcess->gridSize(d, r, c);
      if (d == 0)
	return;

      if (Global::updateViewer())
	m_renderProcess->startRender();
    }
}

void
Viewer::keyPressEvent(QKeyEvent *event)
{
  if (event->key() == Qt::Key_Escape)
    {
      emit quitMahaDrishti();
    }

  // process clipplane events
  if (GeometryObjects::clipplanes()->keyPressEvent(event))
    {
      updateGL();
      return;
    }

  if (GeometryObjects::clipplanes()->grabsMouse())
    {
      if (GeometryObjects::clipplanes()->keyPressEvent(event))
	{
	  updateGL();
	  return;
	}
    }

  if (event->key() == Qt::Key_V)
    {
      GeometryObjects::showGeometry = ! GeometryObjects::showGeometry;

      if (GeometryObjects::showGeometry)
	GeometryObjects::show();
      else
	GeometryObjects::hide();

      updateGL();
      return;
    }

  if (m_renderProcess->screenBoxGrabsMouse())
    {
      if (m_renderProcess->screenBoxKeyPressEvent(event))
	{
	  updateGL();
	  return;
	}
    }

  if (event->key() == Qt::Key_PageUp)
    {
      m_renderProcess->changeStep(-1); // decrease stepsize
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_PageDown)
    {
      m_renderProcess->changeStep(1); // increase stepsize
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Up)
    {
      m_renderProcess->changeTimestep(1); // next timestep
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_Right || event->key() == Qt::Key_Down)
    {
      m_renderProcess->changeTimestep(-1); // previous timestep
      updateGL();
      return;
    }
  else if (event->modifiers() & Qt::ControlModifier)
    {
      if (event->key() == Qt::Key_C)
	{
	  m_renderProcess->saveImageToClipboard();
	  return;
	}
    }
  else if (event->key() == Qt::Key_F)
    {
      m_renderProcess->toggleShowBufferOnly();
      return;
    }
  else if (event->modifiers() & Qt::AltModifier)
    {
      if (event->key() == Qt::Key_S)
	{
	  m_renderProcess->saveImage();
	  return;
	}
    }
  else if (event->key() == Qt::Key_F1)
    {
      
      if (qApp->keyboardModifiers() & Qt::ShiftModifier)
	m_renderProcess->toggleBestLodAlways(true);
      else
	m_renderProcess->toggleBestLodAlways(false);
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_F2)
    {
      m_renderProcess->toggleBounds();
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_F3)
    {
      m_renderProcess->toggleSurfaceType();
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_B)
    {
      if (qApp->keyboardModifiers() & Qt::ShiftModifier)
	m_renderProcess->toggleOuterBox();
      else
	m_renderProcess->toggleBox();
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_D)
    {
      m_renderProcess->toggleDepthcue();
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_H)
    {
      m_renderProcess->addScreenBox();
      return;
    }
  else if (event->key() == Qt::Key_P)
    {
      m_renderProcess->togglePaintUsingScreenBox();
      return;
    }
  else if (event->key() == Qt::Key_L)
    {
      m_renderProcess->toggleLight();
      m_renderProcess->toggleShadows();
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_S)
    {
      m_renderProcess->toggleIntermediate();
      return;
    }
  else if (event->key() == Qt::Key_0)
    {
      m_renderProcess->setUserLodLevel(-1, false);
      updateGL();
      return;
    }
  else if (event->key() >= Qt::Key_1 &&
	   event->key() <= Qt::Key_6)
    {
      m_renderProcess->setUserLodLevel(event->key() - Qt::Key_1,
				       false);
      updateGL();
      return;
    }
  else if (event->key() >= Qt::Key_Exclam &&
	   event->key() <= Qt::Key_Percent)
    {
      m_renderProcess->setUserLodLevel(event->key() - Qt::Key_Exclam,
				       true);
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_At) // need to have special one for @
    {
      m_renderProcess->setUserLodLevel(1, true);
      updateGL();
      return;
    }
  else if (event->key() == Qt::Key_Space)
    {
      m_renderProcess->setShowBufferOnly(true);
      commandEditor();
      return;
    }

  QGLViewer::keyPressEvent(event);
}

QList<QString> Viewer::volumeFilename() { return m_volumeFilename; }

void Viewer::loadVolume(QList<QString> flnm)
{
  m_volumeFilename = flnm;
  m_renderProcess->loadVolume(flnm);
}

void
Viewer::loadLookupTable(QList<QImage> image)
{
  m_renderProcess->setLUT(image);
  updateGL();
}

void
Viewer::commandEditor()
{
  PropertyEditor propertyEditor;
  QMap<QString, QVariantList> plist;
  QVariantList vlist;
  vlist.clear();
  plist["command"] = vlist;

  vlist.clear();
  QFile helpFile(":/viewer.help");
  if (helpFile.open(QFile::ReadOnly))
    {
      QTextStream in(&helpFile);
      QString line = in.readLine();
      while (!line.isNull())
	{
	  if (line == "#begin")
	    {
	      QString keyword = in.readLine();
	      QString helptext;
	      line = in.readLine();
	      while (!line.isNull())
		{
		  helptext += line;
		  helptext += "\n";
		  line = in.readLine();
		  if (line == "#end") break;
		}
	      vlist << keyword << helptext;
	    }
	  line = in.readLine();
	}
    }
  
  plist["commandhelp"] = vlist;
  //---------------------

  QStringList keys;
  keys << "command";
  keys << "commandhelp";

  propertyEditor.set("Command Help", plist, keys);

  QMap<QString, QPair<QVariant, bool> > vmap;
  
  if (propertyEditor.exec() == QDialog::Accepted)
    {
      QString cmd = propertyEditor.getCommandString();
      if (!cmd.isEmpty())
	processCommand(cmd);
    }
}  


void
Viewer::processCommand(QString cmd)
{
  bool ok;
  QStringList words = cmd.split(" ", QString::SkipEmptyParts);  
  if (words.count() == 0)
    return;

  if (words[0] == "mode")
    {
      int mode = Enums::RC_VolumeRender;
      if (words.count() > 1)
	{
	  if (words[1] == "slices") mode = Enums::RC_Slices;
	  if (words[1] == "surface") mode = Enums::RC_Isosurface;
	}
      m_renderProcess->setRaycaster(mode);
    }
  else if (words[0] == "interpolatevalues")
    {
      bool flag = true;
      if (words.count() > 1)
	{
	  if (words[1] == "no")
	    flag = false;
	}
      m_renderProcess->setInterpolateValues(flag);	
    }
  else if (words[0] == "isoval" || words[0] == "val")
    {
      if (words.count() > 1) 
	{
	  int isoval = words[1].toInt(&ok);
	  m_renderProcess->setIsovalue(isoval);
	}
    }
  else if (words[0] == "color")
    {
      Vec scolor = m_renderProcess->surfaceColor();
      QColor dcolor = QColor::fromRgbF(scolor.x,
				       scolor.y,
				       scolor.z);
      QColor color = DColorDialog::getColor(dcolor);
      if (color.isValid())
	{
	  float r = color.redF();
	  float g = color.greenF();
	  float b = color.blueF();
	  m_renderProcess->setSurfaceColor(Vec(r,g,b));
	}
    }
  else if (words[0] == "blocks")
    {
      if (words.count() > 1) 
	{
	  int maxblocks = words[1].toInt(&ok);
	  m_renderProcess->setMaxBlocks(maxblocks);
	}
    }
  else if (words[0] == "4dvolsize")
    {
      if (words.count() == 4) 
	{
	  int x, y, z;
	  x = words[1].toInt(&ok);
	  y = words[2].toInt(&ok);
	  z = words[3].toInt(&ok);
	  m_renderProcess->set4DboxSize(Vec(x,y,z));
	}
    }
  else if (words[0] == "boxsize")
    {
      if (words.count() == 2) 
	{
	  int ntimesteps = qMax(1, words[1].toInt(&ok));
	  m_renderProcess->setBoxSize(ntimesteps);
	}
      else if (words.count() == 4) 
	{
	  int x, y, z;
	  x = words[1].toInt(&ok);
	  y = words[2].toInt(&ok);
	  z = words[3].toInt(&ok);
	  m_renderProcess->setBoxSize(Vec(x,y,z));
	}
    }
  else if (words[0] == "timestep")
    {
      if (words.count() == 1) 
	{
	  int nt = m_renderProcess->timeStep();
	  QMessageBox::information(0, "",
				   QString("Current time step is %1").arg(nt));
	}
      else if (words.count() == 2) 
	{
	  int nt = qMax(1, words[1].toInt(&ok));
	  m_renderProcess->setTimeStep(nt);
	}
    }
  else if (words[0] == "mem")
    {
      if (words.count() > 1) 
	{
	  int memsize = words[1].toInt(&ok);
	  m_renderProcess->setMemorySize(memsize);
	}
    }
  else if (words[0] == "tstep")
    {
      if (words.count() > 1) 
	{
	  float tstep  = qBound(0.01f, words[1].toFloat(&ok), 5.0f);
	  m_renderProcess->setStep(tstep);
	}
    }
  else if (words[0] == "tex")
    {
      if (words.count() > 1) 
	{
	  int texsize = words[1].toInt(&ok);
	  m_renderProcess->setTextureSize(texsize);
	}
    }
  else if (words[0] == "scale")
    {
      Vec scale = Vec(1,1,1);
      if (words.count() > 1) scale.x = words[1].toFloat();
      if (words.count() > 2) scale.y = words[2].toFloat();
      if (words.count() > 3) scale.z = words[3].toFloat();	  
      m_renderProcess->setScale(scale);
    }

  updateGL();
}


void Viewer::lightDirectionChanged(Vec dir) { m_renderProcess->updateLightVector(dir); }
void Viewer::applyLighting(bool flag) { m_renderProcess->applyLighting(flag); }
void Viewer::applyEmissive(bool flag) { m_renderProcess->applyEmissive(flag); }
void Viewer::highlights(Highlights hl) { m_renderProcess->updateHighlights(hl); }
void Viewer::applyShadow(bool flag) { m_renderProcess->applyShadows(flag); }
void Viewer::shadowBlur(float val) { m_renderProcess->updateShadowBlur(val); }
void Viewer::shadowScale(float val) { m_renderProcess->updateShadowScale(val); }
void Viewer::shadowFOV(float val) { m_renderProcess->updateShadowFOV(val); }
void Viewer::shadowIntensity(float val) { m_renderProcess->updateShadowIntensity(val); }

void
Viewer::captureKeyFrameImage(int kfn)
{
  uchar *imgdata = new uchar[4*m_windowWidth*m_windowHeight];
  glReadBuffer(GL_BACK);
  glReadPixels(0,
	       0,
	       m_windowWidth,
	       m_windowHeight,
	       GL_RGBA,
	       GL_UNSIGNED_BYTE,
	       imgdata);
  QImage image(imgdata, m_windowWidth, m_windowHeight, QImage::Format_ARGB32);
  image = image.mirrored();
  image = image.rgbSwapped();
  image = image.scaled(100, 100);

  emit replaceKeyFrameImage(kfn, image);
}

void Viewer::setCurrentFrame(int fno) { m_currFrame = fno; }

void
Viewer::updateLookFrom(Vec pos, Quaternion rot)
{
  m_renderProcess->updateLookFrom(pos, rot);
}

void
Viewer::endPlay()
{
  m_saveSnapshots = false;
  Global::setSaveImageType(Global::NoImage);
}

void
Viewer::setKeyFrame(int fno)
{
  // draw image again to get the correct pixmap
  //draw();

  Vec pos;
  Quaternion rot;
  pos = camera()->position();
  rot = camera()->orientation();
	  
  QImage image = m_renderProcess->getImage();
  image = image.scaled(100, 100);

  Vec bs4d = m_renderProcess->boxSize4D();
  int ts = m_renderProcess->timeStep();

  Vec bmin, bmax;
  bounds(bmin, bmax);

  if (bs4d.x > 0.001)
    {
      // we are handling 4d volume so
      // modify the camera position to make it
      // with respect to the centroid of the bounding box
      Vec cen = (bmax+bmin)/2;
      pos -= cen;

      if (ts >= 0)
	{
	  Vec corner = Vec(bs4d.z, bs4d.y, bs4d.x*ts);
	  bmin -= corner;
	  bmax -= corner;
	}
    }

  float *lut = m_renderProcess->getLUT();

  emit setKeyFrame(pos, rot, fno,
		   bs4d, ts,
		   image,
		   bmin, bmax,
		   lut);
}

void
Viewer::renderDone()
{
  if (m_saveSnapshots)
    saveImage();
  
  if (m_keyFrameEditor->playing())
    emit nextFrame();
}

void Viewer::setImageSize(int w, int h) { }
void Viewer::setImageMode(int im) { m_imageMode = im; }
void Viewer::setImageFileName(QString imgfl)
{
  m_imageFileName = imgfl;
  QFileInfo f(m_imageFileName);
  setSnapshotFormat(f.completeSuffix());
}
void Viewer::setSaveSnapshots(bool flag) { m_saveSnapshots = flag; }
void Viewer::dummydraw() { };

void
Viewer::saveImage()
{
  QString localImageFileName = m_imageFileName;
  QChar fillChar = '0';
  int fieldWidth = 0;
  QRegExp rx("\\$[0-9]*[f|F]");
  if (rx.indexIn(m_imageFileName) > -1)
    {
      localImageFileName.remove(rx);
      
      QString txt = rx.cap();
      if (txt.length() > 2)
	{
	  txt.remove(0,1);
	  txt.chop(1);
	  fieldWidth = txt.toInt();
	}
    }

  saveMonoImage(localImageFileName, fillChar, fieldWidth);
//  if (m_imageMode == Enums::MonoImageMode)
//    saveMonoImage(localImageFileName, fillChar, fieldWidth);
//  else if (m_imageMode == Enums::StereoImageMode)
//    saveStereoImage(localImageFileName, fillChar, fieldWidth);
//  else if (m_imageMode == Enums::CubicImageMode)
//    saveCubicImage(localImageFileName, fillChar, fieldWidth);
}

void
Viewer::saveMonoImage(QString localImageFileName,
		      QChar fillChar, int fieldWidth)
{
  QFileInfo f(localImageFileName);	  

  Global::setSaveImageType(Global::MonoImage);

  //---------------------------------------------------------
  QString imgFile = f.absolutePath() + QDir::separator() +
	            f.baseName();
  if (m_currFrame >= 0)
    imgFile += QString("%1").arg((int)m_currFrame, fieldWidth, 10, fillChar);
  imgFile += ".";
  imgFile += f.completeSuffix();
  //---------------------------------------------------------

  saveSnapshot(imgFile);
}

void
Viewer::saveSnapshot(QString imgFile)
{
  QImage image = m_renderProcess->getImage();
  image.save(imgFile);
}

void
Viewer::update4dInfo(Vec bs4d, int ts, float volInterp)
{
  m_renderProcess->set4DboxSize(bs4d);
  if (ts >= 0)
    {
      m_renderProcess->setTimeStep(ts, true);
      m_renderProcess->setInterpolateValues(volInterp);
    }
}

void Viewer::bounds(Vec &bmin, Vec &bmax) { m_renderProcess->bounds(bmin, bmax); }
void Viewer::setBounds(Vec bmin, Vec bmax) { m_renderProcess->setBounds(bmin, bmax); }

float* Viewer::getLUT() { return m_renderProcess->getLUT(); }
void Viewer::setLUT(float *lut) { m_renderProcess->setLUT(lut); }

void Viewer::set4DboxSize(Vec bs4d) { m_renderProcess->set4DboxSize(bs4d); }
Vec Viewer::boxSize4D() { return m_renderProcess->boxSize4D(); }

void Viewer::setBoxSize(int t) { m_renderProcess->setBoxSize(t); }

int Viewer::timeStep() { return m_renderProcess->timeStep(); }
